package com.geoboard.fragments;

import java.util.ArrayList;
import java.util.Random;

import org.codehaus.jackson.type.TypeReference;

import android.graphics.Color;
import android.location.Location;
import android.net.Uri.Builder;
import android.os.Bundle;
import android.util.Log;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
import android.widget.Toast;

import com.android.volley.Request.Method;
import com.geoboard.HelperVolley;
import com.geoboard.application.GeoBoardApplication;
import com.geoboard.entity.Board;
import com.geoboard.entity.Post;
import com.geoboard.service.BrowseCameraListener;
import com.geoboard.service.BrowseClickListener;
import com.geoboard.service.GetBoardsListener;
import com.geoboard.service.GetPostsListener;
import com.geoboard.service.MarkerDragListener;
import com.geoboard.service.PostBoardListener;
import com.geoboard.service.PostPostListener;
import com.geoboard.service.RequestErrorListener;
import com.geoboard.tools.OverlayFactory;
import com.geoboard.tools.JacksonRequest;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.UiSettings;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.GroundOverlayOptions;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.PolygonOptions;

public class MapFrag extends SupportMapFragment{
	
	/**** Members *************************************************/
	private int MAX_DISPLAYED_BOARDS = 100;
	private int MAX_ZOOM = GeoBoardApplication.MAX_ZOOM;
	private int MIN_ZOOM = GeoBoardApplication.MIN_ZOOM;
	
	private String TAG = this.getClass().getSimpleName();
	private SparseArray<ArrayList<Board>> mRetrievedBoards;
	private ArrayList<Post> mRetrievedPosts;
	private GoogleMap mMap;


	private int mLastMode = -1;
	public final int POST_BROWSE = 0;
	public final int BOARD_BROWSE = 1;
	public final int BOARD_CREATE = 2;

	/**** life cycle methods ***********************************************************/

	@Override
	public void onCreate(Bundle savedInstanceState){
		Log.d(TAG, "onCreate()");
		super.onCreate(savedInstanceState);

		mRetrievedBoards = new SparseArray<ArrayList<Board>>();
		for (int i = MIN_ZOOM; i <= MAX_ZOOM; i++)
			mRetrievedBoards.put(i, new ArrayList<Board>());
	}
	
	@Override
	public void onStart() {
		super.onStart();
		
		mMap = this.getMap();
		
		if(mMap != null){
			mMap.setMyLocationEnabled(true);
			UiSettings ui = mMap.getUiSettings();
			ui.setZoomControlsEnabled(false);
			ui.setMyLocationButtonEnabled(true);
		}
		
		//deep paths for map controls
        ViewGroup v1 = (ViewGroup)this.getView();
        ViewGroup v2 = (ViewGroup)v1.getChildAt(0);
        ViewGroup v3 = (ViewGroup)v2.getChildAt(0);
        ViewGroup v4 = (ViewGroup)v3.getChildAt(1);

        //my position button
        View position =  (View)v4.getChildAt(0);
        int positionWidth = position.getLayoutParams().width;
        int positionHeight = position.getLayoutParams().height;

        //lay out position button
        RelativeLayout.LayoutParams positionParams = new RelativeLayout.LayoutParams(positionWidth,positionHeight);
        int margin = positionWidth/5;
        positionParams.setMargins(margin, 80, 0, 0);
        positionParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
        positionParams.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE);
        position.setLayoutParams(positionParams);
	}
	
	/**** Map setup methods ***************************************************/
	public void setMode(int mode) {
		if (mode != mLastMode) {
			//hide map objects from last mode
			switch(mLastMode) {
				case POST_BROWSE:
					hidePosts();
					break;
				case BOARD_BROWSE:
					hideBoards(MAX_ZOOM);
					break;
				case BOARD_CREATE:
					break;
			}
			switch(mode) {
				case POST_BROWSE:
					enableMapGestures();
					setPostBrowsingListeners();
					break;
				case BOARD_BROWSE:
					enableMapGestures();
					setBoardBrowsingListeners();
					break;
				case BOARD_CREATE:
					disableMapGestures();
					setBoardCreateListeners();
					break;
			}
			mLastMode = mode;
		}
	}
	
	public void animateCamera(Location location) {
        CameraPosition cp = new CameraPosition.Builder()
        .target(new LatLng(location.getLatitude(),location.getLongitude()))
        .zoom(15)
        .build();     
        mMap.moveCamera(CameraUpdateFactory.newCameraPosition(cp));
	}
	
	/**** Post browsing methods ***********************************************/
	
	public void getPosts(Board board) {
		Log.d(TAG, "getting posts from board " + board.getName());
				setMode(POST_BROWSE);
			
		Builder uriBuilder = new Builder();		
		uriBuilder.encodedPath(GeoBoardApplication.POST_URL);
		uriBuilder.appendQueryParameter("board_id", board.getId().toString());

		JacksonRequest<ArrayList<Post>> request = new JacksonRequest<ArrayList<Post>>(
				Method.GET,
				uriBuilder.build().toString(),
				null,
				new TypeReference<ArrayList<Post>>(){},
				new GetPostsListener(this), 
				new RequestErrorListener(this));
		request.setShouldCache(true);
		HelperVolley.getRequestQueue().add(request);	
		
        Toast.makeText(getActivity(),
        		"getting posts", Toast.LENGTH_SHORT).show();
	}
	
	public void displayPosts() {
		Log.d(TAG, String.format("displaying %d posts", mRetrievedPosts.size()));	
		for (Post post : mRetrievedPosts) {
			if (post.getMarker() == null) {
				post.setMarker(mMap.addMarker(new MarkerOptions()
					.position(new LatLng(post.getLat(), post.getLng()))
					.title(post.getUserName())
					.snippet(post.getText())));
			}
		}
	}
	
	public void hidePosts() {
		Log.d(TAG, String.format("hiding %d posts", mRetrievedPosts.size()));	
		for (Post post : mRetrievedPosts){
			if(post.getMarker() != null) {
				post.getMarker().remove();
			}
		}
	}
	
	/**** Post creation methods ***********************************************/
	
	public void createPost (String text, long boardId) {
		Log.d(TAG, "creating post");
		Post post = new Post (boardId, text);
		post.setUserName(GeoBoardApplication.getUser().getUserName());
		post.setLat(mMap.getCameraPosition().target.latitude);
		post.setLng(mMap.getCameraPosition().target.longitude);
		JacksonRequest<Post> request = new JacksonRequest<Post>(
								Method.POST,
								GeoBoardApplication.POST_URL,
								post,
								new TypeReference<Post>(){},
								new PostPostListener(this), 
								new RequestErrorListener(this));
		request.setShouldCache(false);
		HelperVolley.getRequestQueue().add(request);
	}

	
	/**** Board browsing methods **********************************************/
	
	public void getBoards() {
		Log.d(TAG, "getting boards");
		setMode(BOARD_BROWSE);
		
		LatLngBounds range = mMap.getProjection().getVisibleRegion().latLngBounds;
		int zoom = Double.valueOf(mMap.getCameraPosition().zoom).intValue();
		String revealed = "0";
		//get list of boards revealed at this zoom level and range, so no duplicate traffic
		for (Board board : getVisibleBoards(range, zoom))
				revealed += "," + board.getId().toString();
		
		//build url
		Builder uriBuilder = new Builder();
		uriBuilder.encodedPath(GeoBoardApplication.BOARD_URL)
			.appendQueryParameter("n", Double.toString(Math.ceil(range.northeast.latitude)))
			.appendQueryParameter("e", Double.toString(Math.ceil(range.northeast.longitude)))
			.appendQueryParameter("w", Double.toString(Math.floor(range.southwest.longitude)))
			.appendQueryParameter("s", Double.toString(Math.floor(range.southwest.latitude)))
			.appendQueryParameter("zoom", Integer.toString(zoom));
		String cacheKey = uriBuilder.build().toString();
		uriBuilder.appendQueryParameter("revealed", revealed);

		//send request
		JacksonRequest<ArrayList<Board>> request = new JacksonRequest<ArrayList<Board>>(
												Method.GET,
												uriBuilder.build().toString(),
												null,
												new TypeReference<ArrayList<Board>>(){},
							                    new GetBoardsListener(this),
                                                new RequestErrorListener(this));
		request.setShouldCache(true);
		request.setCacheKey(cacheKey);
        HelperVolley.getRequestQueue().add(request);
	}
	
	public void displayBoards() {
		Log.d(TAG, "displaying boards");
		int zoom = Float.valueOf(mMap.getCameraPosition().zoom).intValue();
		int boardCount = 0;
		
		//hide boards at a higher zoom level
		hideBoards(zoom);
		
		//show all of the rest of the boards
		for (; boardCount < MAX_DISPLAYED_BOARDS && zoom <= MAX_ZOOM; zoom++, boardCount++) {
			for (Board board : mRetrievedBoards.get(zoom)) {
				if (board.getOverlay() == null) {
					drawBoard(board);
				}
				board.getOverlay().setVisible(true);
			}
		}
	}
	
	public void drawBoard(Board board) {
		LatLng p1 = new LatLng(board.getNorth(), board.getEast());
		LatLng p2 = new LatLng(board.getSouth(), board.getWest());
		Random rnd = new Random(); 
		int color = Color.argb(88, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256));  
		/*PolygonOptions polyOps = new PolygonOptions()
		.add(p1, 
			new LatLng(p1.latitude, p2.longitude),
			p2,
			new LatLng(p2.latitude, p1.longitude))
		.fillColor(color)
		.strokeWidth(1);
		board.setPolygon(mMap.addPolygon(polyOps));*/
		GroundOverlayOptions goo = OverlayFactory.genBoardOverlay(board.getName(),
				p1, p2, color, mMap);
		
		board.setOverlay(mMap.addGroundOverlay(goo));
	}
	
	//hides all boards between MIN_ZOOM and passed parameter max_zoom
	public void hideBoards(int max_zoom) {
		for (int i = MIN_ZOOM; i < max_zoom; i++) {
			for (Board board : mRetrievedBoards.get(i)) {
				if (board.getOverlay() == null) {
					drawBoard(board);
				}
				board.getOverlay().setVisible(false);
			}
		}
	}
		
	/**** Board creation methods ***********************************************/
	
	public void dropBoardCreationMarkers() {
		Log.d(TAG, "dropping markers for board creation");
	    setMode(BOARD_CREATE);
	}
	
	public void createBoard(String boardName, LatLng marker1, LatLng marker2) {
		Log.d(TAG, "creating board");
		//Marker marker1 = mMarkerDragListener.getBoardCreationMarkers()[0];
		//Marker marker2 = mMarkerDragListener.getBoardCreationMarkers()[1];
		if (marker1 != null && marker2 != null) {
			double lat1 = marker1.latitude;
			double lat2 = marker2.latitude;
			double lng1 = marker1.longitude;
			double lng2 = marker2.longitude;
			Board board = new Board (boardName, GeoBoardApplication.getUser().getUserName());
			board.setNorth(lat1 > lat2 ? lat1 : lat2); 
			board.setSouth(lat1 > lat2 ? lat2 : lat1);
			board.setEast(lng1 > lng2 ? lng1 : lng2); 
			board.setWest(lng1 > lng2 ? lng2 : lng1);
			board.setZoom(Double.valueOf(mMap.getCameraPosition().zoom).intValue());
			JacksonRequest<Board> request = new JacksonRequest<Board>(
									Method.POST,
									GeoBoardApplication.BOARD_URL,
									board,
									new TypeReference<Board>(){},
									new PostBoardListener(this), 
									new RequestErrorListener(this));
			request.setShouldCache(false);
			HelperVolley.getRequestQueue().add(request);
		}
	}
	
	/**** Getters and setters *************************************************/
	public SparseArray<ArrayList<Board>> getRevealedBoards () {
		return mRetrievedBoards;
	}
		
	//getting the map projection is expensive, use getVisibleBoards(LatLngBounds, Int) if possible
	public ArrayList<Board> getVisibleBoards() {
		LatLngBounds range =  mMap.getProjection().getVisibleRegion().latLngBounds;
		int zoom = Float.valueOf(mMap.getCameraPosition().zoom).intValue();
		ArrayList<Board> boards = new ArrayList<Board>();
		for (; zoom < MAX_ZOOM; zoom++)
			for (Board board : mRetrievedBoards.get(zoom))
				if (board.intersectsRect(range))
					boards.add(board);
		return boards;
	}
	
	public ArrayList<Board> getVisibleBoards(LatLngBounds range, int zoom) {
		ArrayList<Board> boards = new ArrayList<Board>();
		for (; zoom < MAX_ZOOM; zoom++)
			for (Board board : mRetrievedBoards.get(zoom))
				if (board.intersectsRect(range))
					boards.add(board);
		return boards;
	}
	
	public ArrayList<Post> getVisiblePosts(){
		return mRetrievedPosts;
	}
	
	public void setRetrievedPosts(ArrayList<Post> posts){
		mRetrievedPosts = posts;
	}	
	
	/**** listener clearing/setting *****************************************/
	
	private void clearListeners() {
		mMap.setOnCameraChangeListener(null);
		mMap.setOnMapClickListener(null);
		mMap.setOnMarkerDragListener(null);
	}
	
	private void setPostBrowsingListeners() {
		clearListeners();	
	}
	
	private void setBoardCreateListeners() {
		clearListeners();
		//mMarkerDragListener = new MarkerDragListener(this);
	    //mMap.setOnMarkerDragListener(mMarkerDragListener);
	}
	
	private void setBoardBrowsingListeners() {
		clearListeners();
		mMap.setOnCameraChangeListener(new BrowseCameraListener(this));
		mMap.setOnMapClickListener(new BrowseClickListener(this));
	}
	
	private void disableMapGestures() {
		mMap.getUiSettings().setScrollGesturesEnabled(false);
		mMap.getUiSettings().setRotateGesturesEnabled(false);
		mMap.getUiSettings().setZoomGesturesEnabled(false);
	}
	
	private void enableMapGestures() {
		mMap.getUiSettings().setScrollGesturesEnabled(true);
		mMap.getUiSettings().setRotateGesturesEnabled(true);
		mMap.getUiSettings().setZoomGesturesEnabled(true);
	}
}